# Single layer neuro network
class SLR
    attr_reader :n,:m

    def initialize(n,m)
        @n, @m = n, m
        @body = Array.new(n).map! { Array.new(m) }
        @body.each do |x|
            x.map! do |y|
                y = rand(0)
            end
        end
    end

    def activate(input)
        output = Array.new(@n, 0)
        @n.times do |i|
            @m.times do |j|
                output[i] += input[j] * @body[i][j]
            end
        end
        output
    end

    def learn(input, output, res)
        delta = (res == :good) ? 0.2 : -0.005
        i = output.index(output.max)
        @m.times do |j|
            @body[i][j] += input[j] * delta
            @body[i][j] = 1 if @body[i][j] > 1 
            @body[i][j] = 0 if @body[i][j] < 0
        end
    end

    def mutate
        @n.times do |i|
            @m.times do |j|
                @body[i][j] += (rand(3) - 1) * 0.1
                @body[i][j] = 1 if @body[i][j] > 1 
                @body[i][j] = 0 if @body[i][j] < 0
            end
        end
    end

    def print(top,left)
        indent = top.size + 6
        printf("%#{indent}s %#{indent}s %#{indent}s\n", "Herbivorious", "Predator", "Grass")
        printf("%6s", "");
        top.each {|x| printf("%4s", x) } 
        printf("\n");
        @body.each_with_index do |x,i|
            printf("%4s : ", left[i]);
            x.each do |y|
                printf("%4.1f", y)
            end
            printf("\n")
        end 
    end
end

class TwoLayerPerceptron
    @@eta = 0.8

    def initialize(i,o)
        @i, @o = i, o

        @w1 = Array.new(i).map! { Array.new(i) }
        @w1.each do |x|
            x.map! do |y|
                y = rand(0)
            end
        end
        
        @w2 = Array.new(i).map! { Array.new(o) }
        @w2.each do |x|
            x.map! do |y|
                y = rand(0)
            end
        end
    end
    
    def f(x)
        #Sigmoid
        1 / (1 + 100 * Math.exp(-x))
        #Reccomended by Voronin tahn
        #Math.tanh(0.003 * (x - 500))
    end

    def activate(input)
        output = Array.new(@o, 0)
        middle = Array.new(@i, 0)

        @i.times do |i|
            @i.times do |j|
                middle[j] += input[i] * @w1[i][j]
            end
        end

        @i.times do |j|
            middle[j] = f(middle[j])
        end

        @i.times do |i|
            @o.times do |j|
                output[j] += middle[i] * @w2[i][j]
            end
        end
        
        @o.times do |j|
            output[j] = f(output[j])
        end

        output
    end 

    def learn(input, etalon, eta)
        #forawrd
        output = Array.new(@o, 0)
        middle = Array.new(@i, 0)

        @i.times do |i|
            @i.times do |j|
                middle[j] += input[i] * @w1[i][j]
            end
        end

        @i.times do |j|
            middle[j] = f(middle[j])
        end

        @i.times do |i|
            @o.times do |j|
                output[j] += middle[i] * @w2[i][j]
            end
        end
        
        @o.times do |j|
            output[j] = f(output[j])
        end

        #backward
        delta2 = Array.new(@o, 0)
        deltaw2 = Array.new(@i).map! { Array.new(@o, 0) }

        # Forms delta for each output neuron
        @o.times do |i|
            delta2[i] = output[i] * (1 - output[i]) * (etalon[i] - output[i])
        end

        # Calculates new weights between layer 1 and 2
        @i.times do |i|     
            @o.times do |j|
                deltaw2[i][j] = eta * delta2[j] * middle[i]
                @w2[i][j] += deltaw2[i][j]
            end
        end

        delta1 = Array.new(@i, 0)
        deltaw1 = Array.new(@i).map! { Array.new(@i, 0) }

        # Forms delta for each neuron in the hidden layer
        @i.times do |i| 
            @o.times do |j|
                delta1[i] += @w2[i][j] * delta2[j]
            end
        end

        @i.times do |i| 
            delta1[i] *= (middle[i]) * (1 - middle[i])
        end

        # Calculates new weights between layer 0 and 1
        @i.times do |i|
            @i.times do |j|
                deltaw1[i][j] = eta * delta1[j] * input[i]
            end
        end

        @i.times do |i|
            @i.times do |j|
                @w1[i][j] += deltaw1[i][j]
            end
        end
    end

    def print
        printf("\nLayer 1:\n")
        @i.times do |i|
            @i.times do |j|
                printf("%.2f  ", @w1[i][j])
            end
            printf("\n")
        end
        printf("\nLayer 2:\n")
        @i.times do |i|
            printf("%.2f  ", @w2[i][0])
        end
    end

    def read_from_file(fname)
        file = File.open(fname)
        lines = file.readlines
        @i = lines[0].to_i
        @o = lines[1].to_i
        
        @w1 = Array.new(@i).map! { Array.new(@i) }
        @w1.each do |x|
            x.map! do |y|
                y = rand(0)
            end
        end
        
        @w2 = Array.new(@i).map! { Array.new(@o) }
        @w2.each do |x|
            x.map! do |y|
                y = rand(0)
            end
        end

        2.upto(lines.size - @i*@o - 1) do |i|
            arr = lines[i].split("  ")
            arr = arr[0..-2]
            arr.map! { |e| e = e.to_f }
            @w1[i - 2] = arr
        end
        
        (lines.size - @i*@o).upto(lines.size - 1) do |i|
            arr = lines[i].split("  ")
            arr = arr[0..-2]
            arr.map! { |e| e = e.to_f }
            @w2[i - lines.size + @i*@o] = arr
        end
        file.close
    end

    def save_to_file(fname)
        file = File.new(fname, "w+")
        file.printf("%d\n%d\n", @i, @o)
        @i.times do |i|
            @i.times do |j|
                file.printf("%.4f  ", @w1[i][j])
            end
            file.printf("\n")
        end
        @i.times do |i|
            @o.times do |j|
                file.printf("%.4f  ", @w2[i][j])
            end
            file.printf("\n")
        end
        file.close
    end
end
